feat(claude): emit plugin dependencies (#187 slice 1) - #290
Conversation
Validate Claude dependency declarations before packaging and preserve them in native and unified plugin manifests with pinned host evidence.
🦋 Changeset detectedLatest commit: dac4f27 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dac4f278bf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| const plugin = { | ||
| author: { name: model.metadata.name }, | ||
| ...(dependencies.document === undefined ? {} : { dependencies: dependencies.document }), |
There was a problem hiding this comment.
Emit marketplace metadata needed to resolve dependencies
On a fresh installation using the generated INSTALL.md, neither dependency form is generally auto-installable: bare names resolve in the generated declaring marketplace, whose plugins array contains only the declaring plugin, while cross-marketplace objects require the root marketplace's allowCrossMarketplaceDependenciesOn field according to claude-2.1.250.json, but the emitted marketplace and its schema omit that field. The manifest therefore advertises dependencies that become dependency-unsatisfied or are blocked unless users manually install them first; emit the required marketplace entries/allowlist or reject declarations the generated distribution cannot resolve.
Useful? React with 👍 / 👎.
| if (name === model.metadata.name) { | ||
| diagnostics.push(dependencyDiagnostic( | ||
| 'claude.dependencies.self', | ||
| `Claude plugin ${JSON.stringify(model.metadata.name)} cannot depend on itself.`, | ||
| 'Remove the self-dependency; self-dependencies can deadlock plugin enable and disable operations.', | ||
| )); | ||
| continue; |
There was a problem hiding this comment.
Allow same-named plugins from another marketplace
When a plugin such as review-tools declares { marketplace: "acme-shared", name: "review-tools" }, this unconditional name comparison reports a self-dependency even though the explicit marketplace gives the dependency a different identity. The implementation itself deduplicates by marketplace/name pairs, and only an omitted marketplace resolves beside the declaring plugin, so the self check should account for the marketplace rather than rejecting every cross-marketplace name collision.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2530cc32. The self-dependency diagnostic now applies only to same-marketplace resolution, so { marketplace: "acme-shared", name: "<own-name>" } is preserved and covered.
| export const isValidClaudeDependencyRange = (value: string): boolean => { | ||
| if (value.length === 0 || value.trim() !== value) return false; | ||
| for (const clause of value.split('||')) { | ||
| const range = clause.trim(); | ||
| if (range.length === 0) return false; | ||
| if (hyphenRangePattern.test(range)) continue; |
There was a problem hiding this comment.
Accept standalone npm wildcard ranges
For a dependency version of "*" or "x", which are valid npm-style semver ranges, semverRangeVersion requires a leading numeric identifier, so this validator returns false and suppresses the entire dependency document with claude.dependencies.version.invalid. Since the new surface promises npm-style ranges and explicitly describes x-wildcards, accept standalone wildcard ranges rather than rejecting valid host input.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2530cc32. isValidClaudeDependencyRange now accepts standalone *, x, and X clauses, with direct regression cases.
Summary
claude.dependenciesconfig surface: the documented union of bare plugin-name strings and closed{ name, version?, marketplace? }objects, validated at plan time and emitted into the generated.claude-plugin/plugin.jsondependenciesarray preserving authored order.claude.dependencies.*diagnostics with recovery text: declaration/entry shape, closed object fields, plugin-name pattern (pinned to the manifest's existing lowercase kebab-case rule, recorded in PROVENANCE), duplicate name+marketplace pairs, self-dependency rejection, marketplace shape, and semver-range syntax. The range validator accepts the documented npm-style grammar (~,^,>=,<=,>,<,=, bare/partial versions, x-wildcards, hyphen ranges, comparator intersections,||unions, pre-release opt-in like^2.0.0-0) without resolving versions — an invalid range would otherwise surface host-side asrange-conflictonly after distribution.dependencydefinition (PROVENANCE notes record that range grammar stays plan-time validation because JSON Schema cannot honestly encode npm range syntax); schema rehash covers the userConfig + dependencies union after rebase.dependenciesdriven by new pinnedplugin.dependenciesfacts (entry forms, semver/pre-release rules,{name}--v{version}tag convention, auto-install with command-source/headersHelper exceptions, root-marketplaceallowCrossMarketplaceDependenciesOnallowlisting, range intersection, prune, and the four documented error codes) with six dated 2026-09-01 evidence lines incl. the local 2.1.257 host proof; honestunavailablemirror on the unified bundle. Marketplace-sideallowCrossMarketplaceDependenciesOnemission is intentionally deferred to the slice-3 marketplace pass.Part of #187 slice 1 (systematic Claude host-parity pass).
Test plan
pnpm typecheckpnpm lint(0 errors, 0 warnings, 977 files)